/**
 * Galog implementaion
 *
 * \file galog.cpp
 * \author tomby
 * \date 04-01-2009
 **/

/*  Copyright (C) 2009  Tomas 'tomby' Bily

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include "dbc-sys.hpp"

using namespace dbc::peel;
using namespace dbc;

class GalogGraph : public SimGraph
{  ENTITY_DEF(GalogGraph);

public:
  GalogGraph ()
  {
    ENTITY_CTOR_IMPL (GalogGraph);
    nodes = NodeContainerPtrType (new NodeContainerType ());
    edges = EdgeContainerPtrType (new EdgeContainerType ());
  }

  bool is_dead ();
  void print ();

  DBC_PC_IFACE_EMPTY;
};

#define IS_DBC_GALOG_EDGE(X) IS_DBC_TYPE(X, GalogEdge)
#define DBC_GALOG_EDGE(X) DBC_TYPE(X, GalogEdge)
#define DBC_GALOG_EDGE_PTR(X) DBC_TYPE_PTR(X, GalogEdge)

class GalogEdge : public Edge
{
  ENTITY_DEF(GalogEdge);

private:
  double value;

public:
  GalogEdge ()
    : Edge (Edge::INTERACT, RED, true), value (9)
  { ENTITY_CTOR_IMPL (GalogEdge); }

  virtual void transform ();
  double get_value ()
  { return value; }

  DBC_PC_IFACE_EMPTY;
};

class GalogRule : public Edge
{
  ENTITY_DEF (GalogRule);

protected:
  int ifreq;
  int num_nodes;
  GalogGraph::Ptr gg;

  boost::uniform_int<> un;
  boost::variate_generator<base_generator_type&, boost::uniform_int<> > urn;

public:
  GalogRule ()
    : un (0, 1), urn (global_generator, un)
  { ENTITY_CTOR_IMPL (GalogRule); }

  GalogRule (int ifq, int nn, GalogGraph* ig)
    : Edge (Edge::INTERACT, GREEN, true), ifreq (ifq), num_nodes (nn), gg (ig),
      un (0, ig->end_nodes () - ig->begin_nodes () - 1),
      urn (global_generator, un)
  { ENTITY_CTOR_IMPL (GalogRule); }

  void reschedule ();
  Node::Ptr& get_rand_node ();

  virtual void transform () {}

  DBC_PC_IFACE_EMPTY;
};

class GainRule : public GalogRule
{
  ENTITY_DEF (GainRule);

public:
  GainRule (int ifq, int nn, GalogGraph* ig)
    : GalogRule (ifq, nn, ig)
  { ENTITY_CTOR_IMPL (GainRule); }

  virtual void transform ();
};

class LoseRule : public GalogRule
{
  ENTITY_DEF (LoseRule);

public:
  LoseRule ()
  { ENTITY_CTOR_IMPL (LoseRule); }

  LoseRule (int ifq, int nn, GalogGraph* ig)
    : GalogRule (ifq, nn, ig)
  { ENTITY_CTOR_IMPL (LoseRule); }

  virtual void transform ();  
};

#define IS_DBC_GALOG_MIN_NODE(X) IS_DBC_TYPE(X, GalogMinNode)
#define DBC_GALOG_MIN_NODE(X) DBC_TYPE(X, GalogMinNode)
#define DBC_GALOG_MIN_NODE_PTR(X) DBC_TYPE_PTR(X, GalogMinNode)

class GalogMinNode : public Node
{
  ENTITY_DEF (GalogMinNode);

private:
  double value;

public:
  
  GalogMinNode ()
    : Node (true), value (9)
  { ENTITY_CTOR_IMPL (GalogMinNode); }

  virtual void update_state ();

  double get_value ()
  { return value; }

  void set_value (double v)
  { value = v; }

  DBC_PC_IFACE_EMPTY;
};

/// Implementation

ENTITY_IMPL1 (GalogGraph, "GalogGraph", Node);
ENTITY_IMPL1 (GalogEdge, "GalogEdge", Edge);
ENTITY_IMPL1 (GalogRule, "GalogRule", Edge);
ENTITY_IMPL1 (GainRule, "GainRule", GalogRule);
ENTITY_IMPL1 (LoseRule, "LoseRule", GalogRule);
ENTITY_IMPL1 (GalogMinNode, "GalogMinNode", Node);

bool
GalogGraph::is_dead ()
{
  bool dead = true;

  for (Graph::NodeIterator i = begin_nodes (); i != end_nodes (); ++i)
    if (DBC_GALOG_MIN_NODE_PTR (*i)->get_value () > 1)
      {
	dead = false;
	break;
      }

  return dead;
}

void
GalogGraph::print ()
{
  int j = 0;

  printf ("Time %f\n", t);
  printf ("Nodes:\n");
  for (Graph::NodeIterator i = begin_nodes (); i != end_nodes (); ++i, ++j)
    printf ("Node %d value %f\n", j, DBC_GALOG_MIN_NODE_PTR (*i)->get_value ());  
}

void
GalogEdge::transform ()
{
  Edge::node_iter i = begin_in_node ();

  value = DBC_GALOG_MIN_NODE (*i)->get_value ();

  update_stamp (1.);
}

void
GalogRule::reschedule ()
{ update_stamp (double (ifreq)); }

Node::Ptr&
GalogRule::get_rand_node ()
{
  int i = urn ();
  printf ("picking random node %d\n", i);
  return gg->begin_nodes () [i]; 
}

void
GainRule::transform ()
{
  for (int i = 0; i < num_nodes; i++)
    {
      Node::Ptr np = get_rand_node ();
      double v = DBC_GALOG_MIN_NODE_PTR(np)->get_value ();
      if (v < 9)
	DBC_GALOG_MIN_NODE_PTR(np)->set_value (v + 1);
    }

  reschedule ();
}

void
LoseRule::transform ()
{
  for (int i = 0; i < num_nodes; i++)
    {
      Node::Ptr np = get_rand_node ();
      double v = DBC_GALOG_MIN_NODE_PTR(np)->get_value ();
      if (v > 0)
	DBC_GALOG_MIN_NODE_PTR(np)->set_value (v - 1);
    }

  reschedule ();
}

#define GMAX 1000

void
GalogMinNode::update_state (void)
{
  double min = GMAX;

  for (Node::edge_iter i = begin_in_edge (Edge::INTERACT); i != end_in_edge (Edge::INTERACT); ++i)
    {
      double v = DBC_GALOG_EDGE ((*i))->get_value ();
      if (min > v)
	min =v;
    }
  if (min != GMAX)
    value = min;

  update_stamp (1.);
}

int
main (int argc, char** argv)
{
  GalogGraph::Ptr p_gg (new GalogGraph ());

  p_gg->add_node (new GalogMinNode ());
  p_gg->add_node (new GalogMinNode ());
  p_gg->add_edge (new GalogEdge ());
  p_gg->add_edge (new GainRule (1, 1, p_gg.get ()));
  p_gg->add_edge (new LoseRule (2, 2, p_gg.get ()));

  p_gg->connect (0, 1, 0);
  p_gg->start ();
  while (!p_gg->is_dead ())
    {
      p_gg->step ();
      p_gg->print ();
      sleep (1);
    }

  return 0;
}
